/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Ref, watch, onMounted, onUnmounted } from 'vue';
import dayjs from 'dayjs';
import interact from 'interactjs';
import { Interactable } from '@interactjs/types/index';
import useStore from '../store';
import { IDragData } from '../interface';
import { calcScheduleByEvtOrDrag } from './use-share';

export function useDrag(
  props: any,
  coordinateElement: Ref<HTMLDivElement | null>,
  bodyCanvas: Ref<HTMLCanvasElement | null>,
  taskGhost: Ref<HTMLDivElement | null>,
): {
  handleDragOver: (payload: DragEvent) => void;
  handleDragLeave: (payload: DragEvent) => void;
  handleDrop: (payload: DragEvent) => Promise<void>;
} {
  const { $renderLayer, $config } = useStore();

  const config = $config.getConfig();

  const resources = $renderLayer.getResources();

  const tasks = $renderLayer.getTasks();

  /**
   * @description 处理拖拽经过
   * - 处理从外部拖入时的ghost样式
   * @param {DragEvent} payload
   */
  const handleDragOver = (payload: DragEvent): void => {
    const types = payload.dataTransfer?.types;
    if (types?.includes('task')) {
      const element = document.querySelector('#drag-ghost-date');
      if (element) {
        const { startDate } = calcScheduleByEvtOrDrag(payload, bodyCanvas);
        element.innerHTML = startDate?.format('YYYY-MM-DD HH:mm') || '';
        if (startDate) payload.preventDefault();
      }
    } else if (types?.includes('resource')) {
      payload.preventDefault();
    }
  };

  /**
   * @description 处理拖拽离开
   * - 处理从外部拖入离开时的ghost样式
   * @param {DragEvent} _payload
   */
  const handleDragLeave = (_payload: DragEvent): void => {
    const element = document.querySelector('#drag-ghost-date');
    if (element) element.innerHTML = '';
  };

  /**
   * @description 处理拖拽放置
   * - 处理从外部拖入放置时的逻辑
   * @param {DragEvent} payload
   * @returns {*}
   */
  const handleDrop = async (payload: DragEvent): Promise<void> => {
    payload.preventDefault();
    if (!coordinateElement.value || !bodyCanvas.value) return;
    const str = payload.dataTransfer?.getData('data');
    if (!str) return;
    const dragData: IDragData = JSON.parse(str);
    const { data, scheduleType } = dragData;
    if (!['resource', 'task'].includes(scheduleType)) return;

    if (scheduleType === 'resource') {
      const result = await props.beforeResourceDrop(data);
      if (result.ok && result.data) {
        await props.resourceDropCompleted(result.data);
      }
    } else {
      const { startDate, resource } = calcScheduleByEvtOrDrag(
        payload,
        bodyCanvas,
      );
      // 任务必须放置在任务区
      if (!startDate || !resource) return;
      const result = await props.beforeTaskDrop(
        data,
        startDate.toDate(),
        resource,
      );
      if (result.ok && result.data) {
        await props.taskDropCompleted(result.data);
      }
    }
  };

  /**
   * @description 计算拖拽任务
   * @param {*} event 事件源
   * @returns {*}  {{
   *     end?: dayjs.Dayjs;
   *     start?: dayjs.Dayjs;
   *     allowDrop: boolean;
   *     resourceId: string;
   *     task?: IScheduleTask;
   *   }}
   */
  const calcDragTask = (
    event: any,
  ): {
    end?: dayjs.Dayjs;
    start?: dayjs.Dayjs;
    allowDrop: boolean;
    resourceId: string;
    task?: IScheduleTask;
  } => {
    const originalId = event.target.getAttribute('data-origin-task-id');
    const task = tasks.find(t => t.id === originalId);

    const { offsetLeft, offsetTop, offsetHeight } = taskGhost.value!;

    const { startDate, endDate, resource, position } = calcScheduleByEvtOrDrag(
      event,
      bodyCanvas,
      {
        top: offsetTop,
        height: offsetHeight,
        left: offsetLeft,
      },
      // 拖拽边时为固定资源标识，整体拖拽时由内部根据鼠标位置计算资源
      !event.edges ? undefined : task!.resourceId,
    );

    const start =
      !event.edges || event.edges.top ? startDate : dayjs(task!.start);
    const end = !event.edges || event.edges.bottom ? endDate : dayjs(task!.end);

    // 整体拖动时判断是否可放置，拖拽边时已经在拖拽过程中限制
    const allowDrop = !event.edges ? !!resource && position.x > 0 : true;

    return {
      end,
      task,
      start,
      allowDrop,
      resourceId: resource!.id,
    };
  };

  /**
   * @description 设置任务拖拽阴影文本
   * @param {dayjs.Dayjs} start
   * @param {dayjs.Dayjs} end
   * @returns {*}
   */
  const setTaskGhostText = (start?: dayjs.Dayjs, end?: dayjs.Dayjs): void => {
    if (!taskGhost.value) return;
    const startTime = taskGhost.value.querySelector('.start-time');
    const endTime = taskGhost.value.querySelector('.end-time');
    if (startTime) startTime.innerHTML = start?.format('HH:mm') || '';
    if (endTime) endTime.innerHTML = end?.format('HH:mm') || '';
  };

  /**
   * @description 设置任务Ghost样式
   * @param {*} event
   * @returns {*}
   */
  const setTaskGhostStyle = (event: any): void => {
    if (!taskGhost.value) return;
    const { scaleRange, dragInterval, resourceBodyRowHeight } = config;
    const canvasRect = bodyCanvas.value!.getBoundingClientRect();
    if (!event.edges) {
      // 整体拖拽样式
      const originalId = event.target.getAttribute('data-origin-task-id');
      const taskId = event.target.getAttribute('data-task-id');
      const taskModels = $renderLayer.taskViewModels.filter(
        t => t.originalId === originalId,
      );
      const taskModelIndex = taskModels.findIndex(t => t.id === taskId);

      // 根据跨天状态计算总高和top
      let height: number = 0;
      let top: number = 0;
      taskModels.forEach((task, index) => {
        height += task.height;
        if (index < taskModelIndex) top += task.height;
      });

      const { start, end, allowDrop } = calcDragTask(event);

      Object.assign(taskGhost.value.style, {
        height: `${height}px`,
        width: `${event.rect.width}px`,
        left: `${event.rect.left - canvasRect.left}px`,
        top: `${event.rect.top - canvasRect.top - top}px`,
        cursor: allowDrop ? 'move' : 'no-drop',
      });

      setTaskGhostText(start, end);
    } else {
      // 边拖拽样式
      const rangeHours = scaleRange[1] - scaleRange[0];
      const dragPrecision = (resourceBodyRowHeight / rangeHours) * dragInterval;
      const { height } = event.rect;
      // 最小高为一个拖拽精度
      if (height < dragPrecision) return;
      const y = event.clientY - canvasRect.top;
      const resourceIndex = Math.floor(y / resourceBodyRowHeight);
      const originalId = event.target.getAttribute('data-origin-task-id');
      const task = tasks.find(t => t.id === originalId);
      // 只能在同一个资源中拖拽边
      if (task!.resourceId !== resources[resourceIndex]?.id) return;
      // 按拖拽间隔吸附
      const relativeY = y - resourceIndex * resourceBodyRowHeight;
      const snapped = Math.round(relativeY / dragPrecision) * dragPrecision;
      const top = resourceIndex * resourceBodyRowHeight + snapped;
      const left = event.rect.left - canvasRect.left;
      if (event.edges.top) {
        // 拖拽上边
        const originalBottom =
          event.target.offsetTop + event.target.offsetHeight;
        const newHeight = originalBottom - top;
        Object.assign(taskGhost.value.style, {
          top: `${top}px`,
          left: `${left}px`,
          height: `${newHeight}px`,
          width: `${event.rect.width}px`,
        });
      } else {
        // 拖拽下边
        Object.assign(taskGhost.value.style, {
          left: `${left}px`,
          width: `${event.rect.width}px`,
          top: `${event.target.offsetTop}px`,
          height: `${top - event.target.offsetTop}px`,
        });
      }
      const { start, end } = calcDragTask(event);
      setTaskGhostText(start, end);
    }
  };

  /**
   * @description 处理任务开始拖拽
   * @returns {*}
   */
  const handleTaskDragStart = (event: any): void => {
    // 禁止选择文本，避免拖动时出现选择效果
    document.body.style.userSelect = 'none';
    if (!taskGhost.value) return;

    Object.assign(taskGhost.value.style, {
      display: 'flex',
    });

    // 拖拽Ghost样式
    setTaskGhostStyle(event);
  };

  /**
   * @description 处理任务结束拖拽
   * @param {*} event
   * @returns {*}
   */
  const handleTaskDragEnd = async (event: any): Promise<void> => {
    // 恢复选择文本功能
    document.body.style.userSelect = '';
    if (!taskGhost.value) return;

    const { start, end, task, resourceId, allowDrop } = calcDragTask(event);

    Object.assign(taskGhost.value.style, {
      display: 'none',
      width: '0px',
      height: '0px',
      top: '0px',
      left: '0px',
    });

    // 任务不存在或者不允许放置则取消拖拽
    if (!task || !allowDrop) return;

    const oldResource = resources.find(res => res.id === task.resourceId);
    const newResource = resources.find(res => res.id === resourceId);

    const newTask = {
      ...task,
      resourceId,
      start: start!.toDate(),
      end: end!.toDate(),
    };

    const result = await props.beforeTaskUpdate(
      newTask,
      task,
      newResource,
      oldResource,
    );

    if (result.ok && result.data) {
      await props.taskUpdateCompleted(result.data);
    }
  };

  // Interact 实例
  let interactable: Interactable | undefined;

  /**
   * @description 初始化任务拖拽
   */
  const initTaskDrag = (): void => {
    interactable = interact('.task-resizable')
      .draggable({
        enabled: config.allowDrag,
        modifiers: [
          interact.modifiers.restrict({
            restriction: '.schedule-table-body',
            endOnly: false,
          }),
        ],
        listeners: {
          start: event => handleTaskDragStart(event),
          end: event => handleTaskDragEnd(event),
          move: event => setTaskGhostStyle(event),
        },
      })
      .resizable({
        enabled: config.allowDrag,
        edges: { top: '.resize-top', bottom: '.resize-bottom' },
        modifiers: [
          interact.modifiers.restrict({
            restriction: '.schedule-table-body',
            endOnly: false,
          }),
        ],
        listeners: {
          start: event => handleTaskDragStart(event),
          end: event => handleTaskDragEnd(event),
          move: event => setTaskGhostStyle(event),
        },
      });
  };

  /**
   * @description 更新Interact配置
   */
  const updateInteractOpt = (): void => {
    interactable
      ?.draggable({ enabled: config.allowDrag })
      .resizable({ enabled: config.allowDrag });
  };

  watch(
    () => config.allowDrag,
    () => updateInteractOpt(),
  );

  onMounted(() => initTaskDrag());

  onUnmounted(() => interactable?.unset());

  return { handleDrop, handleDragOver, handleDragLeave };
}
